package hu.arguscab.utils;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A class képes .properties-ekből Objektumokat előállítani.
 * A .properties fájlban található kulcs-nak megfelelő Setter metódusnak léteznie kell
 * pl.: height=800
 * Ekkor az adott osztálynak definiálnia kell egy publikus void setHeight(int) metódust.
 * @author sharp
 */
public class ObjectLoader {
	private static final Logger log = LoggerFactory.getLogger(ObjectLoader.class);

	public static <T> T load( String fileName, Class<T> clazz ) throws IOException, IllegalStateException {
		log.info("Loading {} class from {}", clazz.getSimpleName(), fileName);
		Properties props = new Properties();
		props.load( new FileInputStream(fileName) );
		Object obj;
		try {
			obj = clazz.newInstance();
			for ( Object p : props.keySet() ) {
				String key = (String)p;
				String methodName = getSetterMethodName(key);
				Method m = getMethod(clazz, methodName);
				if ( m == null ) {
					log.warn("There is not a corresponding method: {}", methodName);
					continue;
				}
				String value = props.getProperty(key);
				try {
					Object arg = getValue(m, value);
					setValue(obj, m, arg);
				} catch (IllegalArgumentException ex) {
					throw new IllegalStateException(ex);
				} catch (InvocationTargetException ex) {
					throw new IllegalStateException(ex);
				}
			}
			return (T) obj;
		} catch (InstantiationException ex) {
			throw new IllegalStateException(ex);
		} catch (IllegalAccessException ex) {
			throw new IllegalStateException(ex);
		}
	}

	/**
	 * @param clazz
	 * @param name
	 * @return A "clazz" osztály "name" nevű setter metódusával.
	 */
	private static Method getMethod(Class<?> clazz, String name) {
		Method[] methods = clazz.getDeclaredMethods();
		for ( Method m : methods ) {
			if ( m.getName().equals(name) )
				if ( isValidMethod(m) )
					return m;
		}
		return null;
	}

	/**
	 * @param field
	 * @return a "field" property setter metódusának nevével
	 */
	private static String getSetterMethodName( String field ) {
		field = Character.toUpperCase( field.charAt(0) ) + field.substring(1);
		return "set" + field;
	}

	/**
	 * @param m
	 * @return true, ha nem static, publikus, és 1 paramétert vár el az "m"
	 */
	private static boolean isValidMethod( Method m ) {
		boolean valid = Modifier.isPublic( m.getModifiers() );		// public
		valid &= (m.getParameterTypes().length == 1);				// 1 parameter
		if ( valid == false )
			return false;
		// only primitive types and String are allowed
		/*Class<?> typeClazz = m.getParameterTypes()[0];
		valid &= ( typeClazz.isPrimitive() || typeClazz.equals( String.class ) );*/
		return valid;
	}

	private static Class<?> getParameterType(Method m) {
		return m.getParameterTypes()[0];
	}

	private static Object getValue( Method m, String value ) throws IOException {
		Class<?> typeClazz = getParameterType(m);
		if ( typeClazz.equals( String.class ) )
			return value;
		else if(typeClazz.equals(byte.class))
			return Byte.valueOf(value);
		else if ( typeClazz.equals( char.class ) )
			return Character.valueOf(value.charAt(0));
		else if ( typeClazz.equals( short.class ) )
			return Short.valueOf(value);
		else if ( typeClazz.equals( int.class ) )
			return Integer.valueOf(value);
		else if ( typeClazz.equals( long.class ) )
			return Long.valueOf(value);
		else if ( typeClazz.equals( float.class ) )
			return Float.valueOf(value);
		else if ( typeClazz.equals( double.class ) )
			return Double.valueOf(value);
		else if ( typeClazz.equals( boolean.class ) ) {
			return Boolean.valueOf(value);
		} else if ( "ref:".equals( value.substring(0, 4)) ) {
			return load( value.substring(4), getParameterType(m) );
		}
		return null;
	}

	private static void setValue( Object obj, Method m, Object value ) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Class<?> typeClazz = getParameterType(m);
		log.debug("{}({}: {})", new Object[]{m.getName(), typeClazz.getSimpleName(), value});
		m.invoke(obj, value);
	}

}
